/* src/interfaces/ecpg/pgtypeslib/datetime.c */

#include "postgres_fe.h"

#include <time.h>
#include <ctype.h>
#include <limits.h>

#include "dt.h"
#include "pgtypes_date.h"
#include "pgtypes_error.h"
#include "pgtypeslib_extern.h"

date *
PGTYPESdate_new(void)
{
	date	   *fc_result;

	fc_result = (date *) pgtypes_alloc(sizeof(date));
	/* 结果可以为 NULL 如果我们内存用尽 */
	return fc_result;
}

void PGTYPESdate_free(date * fc_d)
{
	free(fc_d);
}

date
PGTYPESdate_from_timestamp(timestamp fc_dt)
{
	date		fc_dDate;

	fc_dDate = 0;					/* 抑制编译器警告 */

	if (!TIMESTAMP_NOT_FINITE(fc_dt))
	{
		/* 微秒转为天数 */
		fc_dDate = (fc_dt / USECS_PER_DAY);
	}

	return fc_dDate;
}

date
PGTYPESdate_from_asc(char *fc_str, char **fc_endptr)
{
	date		fc_dDate;
	fsec_t		fc_fsec;
	struct tm	fc_tt,
			   *fc_tm = &fc_tt;
	int			fc_dtype;
	int			fc_nf;
	char	   *fc_field[MAXDATEFIELDS];
	int			fc_ftype[MAXDATEFIELDS];
	char		fc_lowstr[MAXDATELEN + MAXDATEFIELDS];
	char	   *fc_realptr;
	char	  **fc_ptr = (fc_endptr != NULL) ? fc_endptr : &fc_realptr;

	bool		fc_EuroDates = false;

	errno = 0;
	if (strlen(fc_str) > MAXDATELEN)
	{
		errno = PGTYPES_DATE_BAD_DATE;
		return INT_MIN;
	}

	if (ParseDateTime(fc_str, fc_lowstr, fc_field, fc_ftype, &fc_nf, fc_ptr) != 0 ||
		DecodeDateTime(fc_field, fc_ftype, fc_nf, &fc_dtype, fc_tm, &fc_fsec, fc_EuroDates) != 0)
	{
		errno = PGTYPES_DATE_BAD_DATE;
		return INT_MIN;
	}

	switch (fc_dtype)
	{
		case DTK_DATE:
			break;

		case DTK_EPOCH:
			if (GetEpochTime(fc_tm) < 0)
			{
				errno = PGTYPES_DATE_BAD_DATE;
				return INT_MIN;
			}
			break;

		default:
			errno = PGTYPES_DATE_BAD_DATE;
			return INT_MIN;
	}

	fc_dDate = (date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) - date2j(2000, 1, 1));

	return fc_dDate;
}

char * PGTYPESdate_to_asc(date fc_dDate)
{
	struct tm	fc_tt,
			   *fc_tm = &fc_tt;
	char		fc_buf[MAXDATELEN + 1];
	int			fc_DateStyle = 1;
	bool		fc_EuroDates = false;

	j2date(fc_dDate + date2j(2000, 1, 1), &(fc_tm->tm_year), &(fc_tm->tm_mon), &(fc_tm->tm_mday));
	EncodeDateOnly(fc_tm, fc_DateStyle, fc_buf, fc_EuroDates);
	return pgtypes_strdup(fc_buf);
}

void PGTYPESdate_julmdy(date fc_jd, int *fc_mdy)
{
	int			fc_y,
				fc_m,
				fc_d;

	j2date((int) (fc_jd + date2j(2000, 1, 1)), &fc_y, &fc_m, &fc_d);
	fc_mdy[0] = fc_m;
	fc_mdy[1] = fc_d;
	fc_mdy[2] = fc_y;
}

void PGTYPESdate_mdyjul(int *fc_mdy, date * fc_jdate)
{
	/* 月份是 mdy[0] */
	/* 日期是 mdy[1] */
	/* 年份是 mdy[2] */

	*fc_jdate = (date) (date2j(fc_mdy[2], fc_mdy[0], fc_mdy[1]) - date2j(2000, 1, 1));
}

int PGTYPESdate_dayofweek(date fc_dDate)
{
	/*
	 * 星期日：0 星期一：1 星期二：2 星期三：3 星期四：4
	 * 星期五：5 星期六：6
	 */
	return (int) (fc_dDate + date2j(2000, 1, 1) + 1) % 7;
}

void PGTYPESdate_today(date * fc_d)
{
	struct tm	fc_ts;

	GetCurrentDateTime(&fc_ts);
	if (errno == 0)
		*fc_d = date2j(fc_ts.tm_year, fc_ts.tm_mon, fc_ts.tm_mday) - date2j(2000, 1, 1);
}

#define PGTYPES_DATE_NUM_MAX_DIGITS		20	/* 对于大多数年份应该足够... */

#define PGTYPES_FMTDATE_DAY_DIGITS_LZ		1	/* LZ 代表“前导零” */
#define PGTYPES_FMTDATE_DOW_LITERAL_SHORT	2
#define PGTYPES_FMTDATE_MONTH_DIGITS_LZ		3
#define PGTYPES_FMTDATE_MONTH_LITERAL_SHORT 4
#define PGTYPES_FMTDATE_YEAR_DIGITS_SHORT	5
#define PGTYPES_FMTDATE_YEAR_DIGITS_LONG	6

int PGTYPESdate_fmt_asc(date fc_dDate, const char *fc_fmtstring, char *fc_outbuf)
{
	static struct
	{
		char	   *format;
		int			component;
	}			fc_mapping[] =
	{
		/*
		 * 格式项必须根据其长度进行排序，因为第一个匹配的模式
		 * 会被其值替换
		 */
		{
			"ddd", PGTYPES_FMTDATE_DOW_LITERAL_SHORT
		},
		{
			"dd", PGTYPES_FMTDATE_DAY_DIGITS_LZ
		},
		{
			"mmm", PGTYPES_FMTDATE_MONTH_LITERAL_SHORT
		},
		{
			"mm", PGTYPES_FMTDATE_MONTH_DIGITS_LZ
		},
		{
			"yyyy", PGTYPES_FMTDATE_YEAR_DIGITS_LONG
		},
		{
			"yy", PGTYPES_FMTDATE_YEAR_DIGITS_SHORT
		},
		{
			NULL, 0
		}
	};

	union un_fmt_comb fc_replace_val;
	int			fc_replace_type;

	int			fc_i;
	int			fc_dow;
	char	   *fc_start_pattern;
	struct tm	fc_tm;

	/* 复制字符串过来 */
	strcpy(fc_outbuf, fc_fmtstring);

	/* 获取日期 */
	j2date(fc_dDate + date2j(2000, 1, 1), &(fc_tm.tm_year), &(fc_tm.tm_mon), &(fc_tm.tm_mday));
	fc_dow = PGTYPESdate_dayofweek(fc_dDate);

	for (fc_i = 0; fc_mapping[fc_i].format != NULL; fc_i++)
	{
		while ((fc_start_pattern = strstr(fc_outbuf, fc_mapping[fc_i].format)) != NULL)
		{
			switch (fc_mapping[fc_i].component)
			{
				case PGTYPES_FMTDATE_DOW_LITERAL_SHORT:
					fc_replace_val.str_val = pgtypes_date_weekdays_short[fc_dow];
					fc_replace_type = PGTYPES_TYPE_STRING_CONSTANT;
					break;
				case PGTYPES_FMTDATE_DAY_DIGITS_LZ:
					fc_replace_val.uint_val = fc_tm.tm_mday;
					fc_replace_type = PGTYPES_TYPE_UINT_2_LZ;
					break;
				case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT:
					fc_replace_val.str_val = months[fc_tm.tm_mon - 1];
					fc_replace_type = PGTYPES_TYPE_STRING_CONSTANT;
					break;
				case PGTYPES_FMTDATE_MONTH_DIGITS_LZ:
					fc_replace_val.uint_val = fc_tm.tm_mon;
					fc_replace_type = PGTYPES_TYPE_UINT_2_LZ;
					break;
				case PGTYPES_FMTDATE_YEAR_DIGITS_LONG:
					fc_replace_val.uint_val = fc_tm.tm_year;
					fc_replace_type = PGTYPES_TYPE_UINT_4_LZ;
					break;
				case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT:
					fc_replace_val.uint_val = fc_tm.tm_year % 100;
					fc_replace_type = PGTYPES_TYPE_UINT_2_LZ;
					break;
				default:

					/*
					 * 不应该发生，还是设置一些值
					 */
					fc_replace_val.str_val = " ";
					fc_replace_type = PGTYPES_TYPE_STRING_CONSTANT;
			}
			switch (fc_replace_type)
			{
				case PGTYPES_TYPE_STRING_MALLOCED:
				case PGTYPES_TYPE_STRING_CONSTANT:
					memcpy(fc_start_pattern, fc_replace_val.str_val,
						   strlen(fc_replace_val.str_val));
					if (fc_replace_type == PGTYPES_TYPE_STRING_MALLOCED)
						free(fc_replace_val.str_val);
					break;
				case PGTYPES_TYPE_UINT:
					{
						char	   *fc_t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);

						if (!fc_t)
							return -1;
						snprintf(fc_t, PGTYPES_DATE_NUM_MAX_DIGITS,
								 "%u", fc_replace_val.uint_val);
						memcpy(fc_start_pattern, fc_t, strlen(fc_t));
						free(fc_t);
					}
					break;
				case PGTYPES_TYPE_UINT_2_LZ:
					{
						char	   *fc_t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);

						if (!fc_t)
							return -1;
						snprintf(fc_t, PGTYPES_DATE_NUM_MAX_DIGITS,
								 "%02u", fc_replace_val.uint_val);
						memcpy(fc_start_pattern, fc_t, strlen(fc_t));
						free(fc_t);
					}
					break;
				case PGTYPES_TYPE_UINT_4_LZ:
					{
						char	   *fc_t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);

						if (!fc_t)
							return -1;
						snprintf(fc_t, PGTYPES_DATE_NUM_MAX_DIGITS,
								 "%04u", fc_replace_val.uint_val);
						memcpy(fc_start_pattern, fc_t, strlen(fc_t));
						free(fc_t);
					}
					break;
				default:

					/*
					 * 不会发生（我们在上述错误的情况下将 replace_type 设置为
					 * PGTYPES_TYPE_STRING_CONSTANT）
					 */
					break;
			}
		}
	}
	return 0;
}


/*
 * PGTYPESdate_defmt_asc
 *
 * 函数工作原理如下：
 *	 - 首先我们分析参数
 *	 - 如果这是一个没有分隔符的特殊情况，添加分隔符
 *	 - 查找标记。首先我们查找数值。如果我们找到的标记少于
 *	   3 个，则检查月份名称，然后检查月份名称的缩写。
 *	 - 然后我们查看哪个参数应为日期、月份和
 *	   年份，根据这些值计算日期
 */

#define PGTYPES_DATE_MONTH_MAXLENGTH		20	/* 可能还更少 :-) */
int PGTYPESdate_defmt_asc(date * fc_d, const char *fc_fmt, const char *fc_str)
{
	/*
	 * token[2] = { 4,6 } 表示标记 2 从位置 4 开始，到
	 * （包括）位置 6 结束
	 */
	int			fc_token[3][2];
	int			fc_token_values[3] = {-1, -1, -1};
	char	   *fc_fmt_token_order;
	char	   *fc_fmt_ystart,
			   *fc_fmt_mstart,
			   *fc_fmt_dstart;
	unsigned int fc_i;
	int			fc_reading_digit;
	int			fc_token_count;
	char	   *fc_str_copy;
	struct tm	fc_tm;

	fc_tm.tm_year = fc_tm.tm_mon = fc_tm.tm_mday = 0;	/* 保持编译器安静 */

	if (!fc_d || !fc_str || !fc_fmt)
	{
		errno = PGTYPES_DATE_ERR_EARGS;
		return -1;
	}

	/* 分析 fmt 字符串 */
	fc_fmt_ystart = strstr(fc_fmt, "yy");
	fc_fmt_mstart = strstr(fc_fmt, "mm");
	fc_fmt_dstart = strstr(fc_fmt, "dd");

	if (!fc_fmt_ystart || !fc_fmt_mstart || !fc_fmt_dstart)
	{
		errno = PGTYPES_DATE_ERR_EARGS;
		return -1;
	}

	if (fc_fmt_ystart < fc_fmt_mstart)
	{
		/* y m */
		if (fc_fmt_dstart < fc_fmt_ystart)
		{
			/* d y m */
			fc_fmt_token_order = "dym";
		}
		else if (fc_fmt_dstart > fc_fmt_mstart)
		{
			/* y m d */
			fc_fmt_token_order = "ymd";
		}
		else
		{
			/* y d m */
			fc_fmt_token_order = "ydm";
		}
	}
	else
	{
		/* fmt_ystart > fmt_mstart */
		/* m y */
		if (fc_fmt_dstart < fc_fmt_mstart)
		{
			/* d m y */
			fc_fmt_token_order = "dmy";
		}
		else if (fc_fmt_dstart > fc_fmt_ystart)
		{
			/* m y d */
			fc_fmt_token_order = "myd";
		}
		else
		{
			/* m d y */
			fc_fmt_token_order = "mdy";
		}
	}

	/*
	 * 处理没有分隔符的特殊情况。如果我们看到：
	 *
	 * 只有数字，6 或 8 字节，那么它可能是 ddmmyy 和 ddmmyyyy（或
	 * 类似格式）
	 *
	 * 我们将其简化为带有分隔符的字符串并继续处理
	 */

	/* 检查是否只有数字 */
	fc_reading_digit = 1;
	for (fc_i = 0; fc_str[fc_i]; fc_i++)
	{
		if (!isdigit((unsigned char) fc_str[fc_i]))
		{
			fc_reading_digit = 0;
			break;
		}
	}
	if (fc_reading_digit)
	{
		int			fc_frag_length[3];
		int			fc_target_pos;

		fc_i = strlen(fc_str);
		if (fc_i != 8 && fc_i != 6)
		{
			errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
			return -1;
		}
		/* 好吧，这确实是特殊情况 */

		/*
		 * 字符串的长度，一个额外的字节用作终止符，和 2 个
		 * 用于 3 个字段之间的分隔符
		 */
		fc_str_copy = pgtypes_alloc(strlen(fc_str) + 1 + 2);
		if (!fc_str_copy)
			return -1;

		/* 确定片段的长度 */
		if (fc_i == 6)
		{
			fc_frag_length[0] = 2;
			fc_frag_length[1] = 2;
			fc_frag_length[2] = 2;
		}
		else
		{
			if (fc_fmt_token_order[0] == 'y')
			{
				fc_frag_length[0] = 4;
				fc_frag_length[1] = 2;
				fc_frag_length[2] = 2;
			}
			else if (fc_fmt_token_order[1] == 'y')
			{
				fc_frag_length[0] = 2;
				fc_frag_length[1] = 4;
				fc_frag_length[2] = 2;
			}
			else
			{
				fc_frag_length[0] = 2;
				fc_frag_length[1] = 2;
				fc_frag_length[2] = 4;
			}
		}
		fc_target_pos = 0;

		/*
		 * XXX：在这里我们可以计算标记的位置并保存
		 * 下面的 for 循环，在那里我们再次使用 isdigit() 检查
		 * 数字。
		 */
		for (fc_i = 0; fc_i < 3; fc_i++)
		{
			int			fc_start_pos = 0;

			if (fc_i >= 1)
				fc_start_pos += fc_frag_length[0];
			if (fc_i == 2)
				fc_start_pos += fc_frag_length[1];

			strncpy(fc_str_copy + fc_target_pos, fc_str + fc_start_pos,
					fc_frag_length[fc_i]);
			fc_target_pos += fc_frag_length[fc_i];
			if (fc_i != 2)
			{
				fc_str_copy[fc_target_pos] = ' ';
				fc_target_pos++;
			}
		}
		fc_str_copy[fc_target_pos] = '\0';
	}
	else
	{
		fc_str_copy = pgtypes_strdup(fc_str);
		if (!fc_str_copy)
			return -1;

		/* 将整个字符串转换为小写 */
		for (fc_i = 0; fc_str_copy[fc_i]; fc_i++)
			fc_str_copy[fc_i] = (char) pg_tolower((unsigned char) fc_str_copy[fc_i]);
	}

	/* 查找数值标记 */
	fc_reading_digit = 0;
	fc_token_count = 0;
	for (fc_i = 0; fc_i < strlen(fc_str_copy); fc_i++)
	{
		if (!isdigit((unsigned char) fc_str_copy[fc_i]) && fc_reading_digit)
		{
			/* 标记已完成 */
			fc_token[fc_token_count][1] = fc_i - 1;
			fc_reading_digit = 0;
			fc_token_count++;
		}
		else if (isdigit((unsigned char) fc_str_copy[fc_i]) && !fc_reading_digit)
		{
			/* 我们找到了一条标记 */
			fc_token[fc_token_count][0] = fc_i;
			fc_reading_digit = 1;
		}
	}

	/*
	 * 我们在输入字符串的末尾，但也许我们还在读取一个
	 * 数字...
	 */
	if (fc_reading_digit)
	{
		fc_token[fc_token_count][1] = fc_i - 1;
		fc_token_count++;
	}


	if (fc_token_count < 2)
	{
		/*
		 * 没有找到所有标记，无法通过字符串
		 * 匹配找到 2 个缺失的标记
		 */
		free(fc_str_copy);
		errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
		return -1;
	}

	if (fc_token_count != 3)
	{
		/*
		 * 没有找到所有标记，但我们可以通过测试
		 * 月份名称和月份缩写来找到另一个
		 */
		char	   *fc_month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
		char	   *fc_start_pos;
		int			fc_j;
		int			fc_offset;
		int			fc_found = 0;
		char	  **fc_list;

		if (!fc_month_lower_tmp)
		{
			/* 释放我们之前分配的变量 */
			free(fc_str_copy);
			return -1;
		}
		fc_list = pgtypes_date_months;
		for (fc_i = 0; fc_list[fc_i]; fc_i++)
		{
			for (fc_j = 0; fc_j < PGTYPES_DATE_MONTH_MAXLENGTH; fc_j++)
			{
				fc_month_lower_tmp[fc_j] = (char) pg_tolower((unsigned char) fc_list[fc_i][fc_j]);
				if (!fc_month_lower_tmp[fc_j])
				{
					/* 正确终止 */
					break;
				}
			}
			if ((fc_start_pos = strstr(fc_str_copy, fc_month_lower_tmp)))
			{
				fc_offset = fc_start_pos - fc_str_copy;

				/*
				 * 将新的标记排序到数字标记中，如有必要则进行移动
				 */
				if (fc_offset < fc_token[0][0])
				{
					fc_token[2][0] = fc_token[1][0];
					fc_token[2][1] = fc_token[1][1];
					fc_token[1][0] = fc_token[0][0];
					fc_token[1][1] = fc_token[0][1];
					fc_token_count = 0;
				}
				else if (fc_offset < fc_token[1][0])
				{
					fc_token[2][0] = fc_token[1][0];
					fc_token[2][1] = fc_token[1][1];
					fc_token_count = 1;
				}
				else
					fc_token_count = 2;
				fc_token[fc_token_count][0] = fc_offset;
				fc_token[fc_token_count][1] = fc_offset + strlen(fc_month_lower_tmp) - 1;

				/*
				 * 该值是月份在月份数组中的索引
				 * + 1（1月是第0个月）
				 */
				fc_token_values[fc_token_count] = fc_i + 1;
				fc_found = 1;
				break;
			}

			/*
			 * 恶意[tm] hack：如果我们读取了 pgtypes_date_months 并且没有
			 * 找到匹配项，则重置列表指向月份（缩写）
			 * 并重置计数变量 i
			 */
			if (fc_list == pgtypes_date_months)
			{
				if (fc_list[fc_i + 1] == NULL)
				{
					fc_list = months;
					fc_i = -1;
				}
			}
		}
		if (!fc_found)
		{
			free(fc_month_lower_tmp);
			free(fc_str_copy);
			errno = PGTYPES_DATE_ERR_ENOTDMY;
			return -1;
		}

		/*
		 * 在这里我们找到了一个月份。 token[token_count] 和
		 * token_values[token_count] 反映了该月份的细节。
		 *
		 * 只有月份可以通过字面量指定。在这里我们可以快速检查月份
		 * 是否在格式字符串的正确位置，因为我们可以检查我们期望
		 * 作为月份的标记是否处于唯一已经有值的标记位置。如果我们不在这里检查，
		 * 我们可能会说 "December 4 1990" 但使用格式字符串 "dd mm yy" 
		 * 表示 1990 年 4 月 12 日。
		 */
		if (fc_fmt_token_order[fc_token_count] != 'm')
		{
			/* 待会处理错误 */
			fc_token_values[fc_token_count] = -1;
		}
		free(fc_month_lower_tmp);
	}

	/* 用 ASCII-0 终止标记并获取它们的值 */
	for (fc_i = 0; fc_i < 3; fc_i++)
	{
		*(fc_str_copy + fc_token[fc_i][1] + 1) = '\0';
		/* 一个月已经设置了值，检查 token_value == -1 */
		if (fc_token_values[fc_i] == -1)
		{
			errno = 0;
			fc_token_values[fc_i] = strtol(fc_str_copy + fc_token[fc_i][0], (char **) NULL, 10);
			/* strtol 在出现错误时设置 errno */
			if (errno)
				fc_token_values[fc_i] = -1;
		}
		if (fc_fmt_token_order[fc_i] == 'd')
			fc_tm.tm_mday = fc_token_values[fc_i];
		else if (fc_fmt_token_order[fc_i] == 'm')
			fc_tm.tm_mon = fc_token_values[fc_i];
		else if (fc_fmt_token_order[fc_i] == 'y')
			fc_tm.tm_year = fc_token_values[fc_i];
	}
	free(fc_str_copy);

	if (fc_tm.tm_mday < 1 || fc_tm.tm_mday > 31)
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	if (fc_tm.tm_mon < 1 || fc_tm.tm_mon > MONTHS_PER_YEAR)
	{
		errno = PGTYPES_DATE_BAD_MONTH;
		return -1;
	}

	if (fc_tm.tm_mday == 31 && (fc_tm.tm_mon == 4 || fc_tm.tm_mon == 6 || fc_tm.tm_mon == 9 || fc_tm.tm_mon == 11))
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	if (fc_tm.tm_mon == 2 && fc_tm.tm_mday > 29)
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	*fc_d = date2j(fc_tm.tm_year, fc_tm.tm_mon, fc_tm.tm_mday) - date2j(2000, 1, 1);

	return 0;
}
